package org.ala.tiktools.client_server.bean;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

import org.ala.tiktools.rpc.plugin.node.NodeKey;
import org.ala.tiktools.rpc.plugin.rate_limiter.IRateLimiter;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Component;

/**
 * 	本地滑窗限流器
 * 	<p>	这里仅仅是做个演示
 *
 *	@author ala
 *	@date 2025年5月3日
 */
@Component
public class LocalRatelimiter implements IRateLimiter {
	/**
	 * 	每秒允许的请求数
	 */
	protected int limit;
	/**
	 * 	每个key上次请求信息
	 */
	protected Map<NodeKey, LimiterInfo> limitInfos = new ConcurrentHashMap<>();
	@Override
	public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
		//	初始化方法
	}
	/**
	 * 	@param	key	请求的节点信息
	 */
	@Override
	public long limit(Object[] args, NodeKey key) {
		//	简单实现，如果每秒请求数超了，则该秒剩下的时间不允许请求
		//	这里仅仅是做个演示，以具体请求节点为粒度做限流处理
		long t = System.currentTimeMillis() / 1000;
		LimiterInfo info = limitInfos.computeIfAbsent(key, k -> LimiterInfo.build(t));
		info.lock.lock();
		try {
			if (t != info.t) {
				info.t = t;
				info.lastTimestamp = System.currentTimeMillis();
				info.times = 0;
				return -1;
			} else if (info.times++ < limit) {
				return -1;
			} else {
				//	返回主线程要等待的毫秒数。上层代码会有判断走自旋还是休眠。
				return System.currentTimeMillis() - info.lastTimestamp;
			}
		} finally {
			info.lock.unlock();
		}
	}
	/**
	 * 	每个key的上次请求时间戳和请求次数
	 */
	public static class LimiterInfo {
		long t;
		long lastTimestamp;
		int times;
		Lock lock;
		public static LimiterInfo build(long t) { 
			LimiterInfo info = new LimiterInfo();
			info.t = t;
			info.lastTimestamp = System.currentTimeMillis();
			info.times = 0;
			info.lock = new ReentrantLock();
			return info;
		}
		public LimiterInfo t(int t) {
			this.t = t;
			return this;
		}
		public LimiterInfo lastTimestamp(long lastTimestamp) {
			this.lastTimestamp = lastTimestamp;
			return this;
		}
	}
}
